///\todo upgrade

//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_IMAGE_ALGORITHM_COLOR_H_
#define _BUOLA_IMAGE_ALGORITHM_COLOR_H_

#include <buola/image/algorithm/detail/opencv.h>

namespace buola { namespace img {

//! \name color balance
//@{

inline void white_balance(const CMView_rgb8 &pI,double pAmount=0.01)
{
    int lBuckets[3][256];

    for(int i=0;i<3;i++)
        for(int j=0;j<256;j++)
            lBuckets[i][j]=0;

    int lMin[3],lMax[3];
    for(int y=0;y<pI.Height();++y)
    {
        auto lI=pI.Row(y).begin();

        for(int x=0;x<pI.Width();++x)
        {
            for(int c=0;c<3;c++)
            {
                lBuckets[c][lI[x][c]]++;
            }
        }
    }

    int lThreshold=(int)((pI.Height()*pI.Width())*pAmount);

    for(int c=0;c<3;c++)
    {
        int lCount=0;

        int i=0;

        for(;;++i)
        {
            lCount+=lBuckets[c][i];
            if(lCount>lThreshold) break;
        }

        lMin[c]=i;

        lCount=0;
        i=255;

        for(;;--i)
        {
            lCount+=lBuckets[c][i];
            if(lCount>lThreshold) break;
        }

        lMax[c]=i;

        if(lMax[c]==lMin[c])
        {
            msg_info() << c << "->" << lMax[c] << "\n";
            throw XData("max and min channel values are the same");
        }
    }

    for(int y=0;y<pI.Height();++y)
    {
        auto lI=pI.Row(y).begin();

        for(int x=0;x<pI.Width();++x)
        {
            for(int c=0;c<3;c++)
            {
                lI[x][c]=(unsigned char)(std::max(0,std::min(255,
                    (((int)lI[x][c])-lMin[c])*255/(lMax[c]-lMin[c]))));
            }
        }
    }
}

inline void detect_color(const CView_hsv32f &pView,const CMView_gray8 &pDst,float pHue,float pHueTol=10.0,float pMinSat=0.5,float pMinVal=0.1)
{
    float lHue=pHue/360.0f;
    if(lHue<0.0f)
        lHue=lHue+1.0f;
    float lHueTol=pHueTol/360.0f;

    for (int y=0; y<pView.Height(); ++y)
    {
        auto lSrcIt = pView.Row(y).begin();
        auto lDstIt = pDst.Row(y).begin();

        for (int x=0; x<pView.Width(); ++x)
        {

            if(lSrcIt[x][2]>pMinVal&&lSrcIt[x][1]>pMinSat)
            {
                float lHueDiff=lSrcIt[x][0]-lHue;
                if(lHueDiff>0.5f)
                    lHueDiff-=1.0f;
                else if(lHueDiff<-0.5f)
                    lHueDiff+=1.0f;
                if(std::abs(lHueDiff)<=lHueTol)
                {
                    lDstIt[x]=255;
                }
                else
                {
                    lDstIt[x]=0;
                }
            }
            else
            {
                lDstIt[x]=0;
            }
        }
    }
}

inline int connected_components_first_loop(const CView_gray8 &pSrc,
          const CMView_gray32 &pDst,std::vector<unsigned int> &pBlobs)
{
    pBlobs.push_back(0);

    for(int y=0;y<pSrc.Height();++y)
    {
        auto lSrcIt=pSrc.Row(y).begin();
        auto lDstIt=pDst.Row(y).begin();

        auto lDstItUp=(y>0)?pDst.Row(y-1).begin():pDst.Row(y).begin();

        for(int x=0;x<pSrc.Width();++x)
        {
            if(lSrcIt[x])   //not background
            {
                if(y>0&&lDstItUp[x]>0)
                {
                    lDstIt[x]=pBlobs[lDstItUp[x]];

                    if(x>0&&lDstIt[x-1]>0)
                    {
                        if(lDstIt[x]!=lDstIt[x-1]) //conflict
                        {
                            if(lDstIt[x]<lDstIt[x-1])
                            {
                                pBlobs[lDstIt[x-1]]=pBlobs[lDstIt[x]];
                            }
                            else
                            {
                                pBlobs[lDstIt[x]]=pBlobs[lDstIt[x-1]];
                                lDstIt[x]=pBlobs[lDstIt[x-1]];
                            }
                        }
                    }
                }
                else if(x>0&&lDstIt[x-1]>0)
                {
                    lDstIt[x]=pBlobs[lDstIt[x-1]];
                }
                else
                {
                    lDstIt[x]=pBlobs.size();
                    pBlobs.push_back(lDstIt[x]);
                }
            }
            else
            {
                lDstIt[x]=0;
            }
        }
    }

    int lNRealBlobs=0;

    for(int i=1;i<pBlobs.size();++i)
    {
        if(pBlobs[i]==i)
        {
            lNRealBlobs++;
            pBlobs[i]=lNRealBlobs;
        }
        else
        {
            pBlobs[i]=pBlobs[pBlobs[i]];
        }
    }

    return lNRealBlobs;
}

inline int connected_components(const CView_gray8 &pSrc,
                              const CMView_gray32 &pDst)
{
    std::vector<unsigned int> lBlobs;

    int lNRealBlobs=connected_components_first_loop(pSrc,pDst,lBlobs);

    for(int y=0;y<pSrc.Height();++y)
    {
        auto lDstIt=pDst.Row(y).begin();

        for(int x=0;x<pSrc.Width();++x)
        {
            lDstIt[x]=lBlobs[lDstIt[x]];
        }
    }

    return lNRealBlobs;
}

inline int connected_components(const CView_gray8 &pSrc,
                              const CMView_gray32 &pDst,
                              std::vector<int> &pBlobSize)
{
    std::vector<unsigned int> lBlobs;

    int lNRealBlobs=connected_components_first_loop(pSrc,pDst,lBlobs);

    pBlobSize.clear();
    pBlobSize.resize(lNRealBlobs+1,0);

    for(int y=0;y<pSrc.Height();++y)
    {
        auto lDstIt=pDst.Row(y).begin();

        for(int x=0;x<pSrc.Width();++x)
        {
            if(lDstIt[x])
            {
                lDstIt[x]=lBlobs[lDstIt[x]];
                pBlobSize[lDstIt[x]]++;
            }
        }
    }

    return lNRealBlobs;
}

inline int connected_components(const CView_gray8 &pSrc,
                              const CMView_gray32 &pDst,
                              std::vector<int> &pBlobSize,
                              std::vector<CPoint_d > &pBlobCenters)
{
    std::vector<unsigned int> lBlobs;

    int lNRealBlobs=connected_components_first_loop(pSrc,pDst,lBlobs);

    pBlobSize.clear();
    pBlobSize.resize(lNRealBlobs+1,0);

    std::vector<int> lXSum;
    std::vector<int> lYSum;

    lXSum.resize(lNRealBlobs+1,0);
    lYSum.resize(lNRealBlobs+1,0);

    for(int y=0;y<pSrc.Height();++y)
    {
        auto lDstIt=pDst.Row(y).begin();

        for(int x=0;x<pSrc.Width();++x)
        {
            if(lDstIt[x])
            {
                lDstIt[x]=lBlobs[lDstIt[x]];
                pBlobSize[lDstIt[x]]++;
                lXSum[lDstIt[x]]+=x;
                lYSum[lDstIt[x]]+=y;
            }
        }
    }

    pBlobCenters.resize(lNRealBlobs+1);

    for(int i=0;i<lXSum.size();i++)
    {
        if(pBlobSize[i])
        {
            pBlobCenters[i]=CPoint_d(lXSum[i]/pBlobSize[i],lYSum[i]/pBlobSize[i]);
        }
        else
        {
            pBlobCenters[i]={-1,-1};
        }
    }

    return lNRealBlobs;
}

inline int connected_components(const CView_gray8 &pSrc,
        const CMView_gray32 &pDst,
        std::vector<int> &pBlobSize,
        std::vector<CPoint_d > &pBlobCenters,
        std::vector<CRect_d > &pBlobBoundBox)
{
    std::vector<unsigned int> lBlobs;

    int lNRealBlobs=connected_components_first_loop(pSrc,pDst,lBlobs);

    pBlobBoundBox.clear();
    pBlobBoundBox.resize(lNRealBlobs+1,CRect_d(-1,-1,-1,-1));
    //pBlobBoundBox.resize(lNRealBlobs+1,CRect_d(100000,-1,100000,-1));
    pBlobSize.clear();
    pBlobSize.resize(lNRealBlobs+1,0);

    std::vector<int> lXSum;
    std::vector<int> lYSum;

    lXSum.resize(lNRealBlobs+1,0);
    lYSum.resize(lNRealBlobs+1,0);

    for(int y=0;y<pSrc.Height();++y)
    {
        auto lDstIt=pDst.Row(y).begin();

        for(int x=0;x<pSrc.Width();++x)
        {
            if(lDstIt[x])
            {
                lDstIt[x]=lBlobs[lDstIt[x]];
                pBlobSize[lDstIt[x]]++;
                lXSum[lDstIt[x]]+=x;
                lYSum[lDstIt[x]]+=y;
                if(pBlobBoundBox[lDstIt[x]].b==-1)//UNDEFINED
                    pBlobBoundBox[lDstIt[x]]=CRect_d(x,y,x,y);
                else
                {
                    if(x > pBlobBoundBox[lDstIt[x]].r)
                        pBlobBoundBox[lDstIt[x]].r = x;
                    else if(x < pBlobBoundBox[lDstIt[x]].l)
                        pBlobBoundBox[lDstIt[x]].l = x;
                    if(y > pBlobBoundBox[lDstIt[x]].b)
                        pBlobBoundBox[lDstIt[x]].b = y;
                    else if(y < pBlobBoundBox[lDstIt[x]].t)
                        pBlobBoundBox[lDstIt[x]].t = y;
                }
            }
        }
    }

    pBlobCenters.resize(lNRealBlobs+1);

    for(int i=0;i<lXSum.size();i++)
    {
        if(pBlobSize[i])
        {
            pBlobCenters[i]=CPoint_d(lXSum[i]/pBlobSize[i],lYSum[i]/pBlobSize[i]);
        }
        else
        {
            pBlobCenters[i]={-1,-1};
        }
    }

    return lNRealBlobs;
}

inline void blobs_to_colors(const CView_gray32 &pSrc,
                      const CMView_rgb8 &pDst)
{
    for(int y=0;y<pSrc.Height();++y)
    {
        auto lSrcIt=pSrc.Row(y).begin();
        auto lDstIt=pDst.Row(y).begin();

        for(int x=0;x<pSrc.Width();++x)
        {
            lDstIt[x][0]=(lSrcIt[x]&0x03)<<6;
            lDstIt[x][1]=(lSrcIt[x]&0x0c)<<4;
            lDstIt[x][2]=(lSrcIt[x]&0x30)<<2;
        }
    }
}

inline void blobs_to_colors(const CView_gray8 &pSrc,
                      const CMView_rgb8 &pDst)
{
    for(int y=0;y<pSrc.Height();++y)
    {
        auto lSrcIt=pSrc.Row(y).begin();
        auto lDstIt=pDst.Row(y).begin();

        for(int x=0;x<pSrc.Width();++x)
        {
            lDstIt[x][0]=(lSrcIt[x]&0x03)<<6;
            lDstIt[x][1]=(lSrcIt[x]&0x0c)<<4;
            lDstIt[x][2]=(lSrcIt[x]&0x30)<<2;
        }
    }
}

//!normalizes pDst so that it has the same histogram as pSrc
inline void cross_equalize(const CView_gray8 &pSrc,const CMView_gray8 &pDst)
{
    int lHistS[256],lHistD[256];

    std::fill_n(lHistS,256,0);
    std::fill_n(lHistD,256,0);

    ///\todo
//    for(auto i=pSrc.Begin();i!=pSrc.End();++i)
//        lHistS[*i]++;
//    for(auto i=pDst.Begin();i!=pDst.End();++i)
//        lHistD[*i]++;

    for(int i=1;i<256;i++)
    {
        lHistD[i]+=lHistD[i-1];
    }

//    int lSumH=0,e=0;
//    float lEdg[257];
//    unsigned char lTrans[256];
//    lEdg[0]=0.0;
//    for(int i=0;i<256;i++)
//    {
//        while(lSumH<lHistD[i])
//            lSumH+=lHistS[e++];
//        lEdg[i+1]=e-(float)(lSumH-lHistD[i])/lHistS[e-1];
//        lTrans[i]=(unsigned char)(lEdg[i]+0.5f);
//    }

    ///\todo 
//    for(auto i=pDst.Begin();i!=pDst.End();++i)
//        *i=lTrans[*i];
}

inline void bayer_to_rgb(const CView_gray8 &pSrc,const CMView_rgb8 &pDst)
{
    cv::cvtColor(mat_wrap(pSrc),mat_wrap(pDst),CV_BayerBG2RGB);
}

inline void tint(const CView_rgba8 &pSrc,const CMView_rgba8 &pDst,const img::CColor &pColor)
{
    img::CPixel_rgba8 lColor(pColor);

    for(int y=0;y<pSrc.Height();++y)
    {
        auto lSrcIt=pSrc.Row(y).begin();
        auto lDstIt=pDst.Row(y).begin();

        for(int x=0;x<pSrc.Width();++x)
        {
            lDstIt[x][0]=(uint32_t(lSrcIt[x][0])+lColor[0])>>1;
            lDstIt[x][1]=(uint32_t(lSrcIt[x][1])+lColor[1])>>1;
            lDstIt[x][2]=(uint32_t(lSrcIt[x][2])+lColor[2])>>1;
            lDstIt[x][3]=lSrcIt[x][3];
        }
    }
}

inline void tint(const CMView_rgba8 &pView,const img::CColor &pColor)
{
    img::CPixel_rgba8 lColor(pColor);

    for(int y=0;y<pView.Height();++y)
    {
        auto lIt=pView.Row(y).begin();

        for(int x=0;x<pView.Width();++x)
        {
            lIt[x][0]=(uint32_t(lIt[x][0])+lColor[0])>>1;
            lIt[x][1]=(uint32_t(lIt[x][1])+lColor[1])>>1;
            lIt[x][2]=(uint32_t(lIt[x][2])+lColor[2])>>1;
        }
    }
}

inline void saturate(const CMView_rgba8 &pView,const img::CColor &pColor)
{
    img::CPixel_rgba8 lColor(pColor);

    double h,s,v;
    pColor.GetHSV(h,s,v);
    int lSQ=std::max((int)(90.0*s+55),100);
    int lMax=255+(int)(0.65*(100-lSQ));

    int32_t lR=lColor[0];
    int32_t lG=lColor[1];
    int32_t lB=lColor[2];
    
    for(int y=0;y<pView.Height();++y)
    {
        auto lIt=pView.Row(y).begin();

        for(int x=0;x<pView.Width();++x)
        {
            lIt[x][0]=clamp((lSQ*clamp(lR+lIt[x][0]-128,0,lMax)+(100-lSQ)*lIt[x][0])/100,0,255);
            lIt[x][1]=clamp((lSQ*clamp(lG+lIt[x][1]-128,0,lMax)+(100-lSQ)*lIt[x][1])/100,0,255);
            lIt[x][2]=clamp((lSQ*clamp(lB+lIt[x][2]-128,0,lMax)+(100-lSQ)*lIt[x][2])/100,0,255);
        }
    }
}

//@}
    
/*namespace image*/ } /*namespace buola*/ }

#endif
